/*
 *  Copyright 2011 Alexey Andreev.
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 * 
 *       http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *  under the License.
 */
package org.xthl.core;

import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import org.nop.core.Application;
import org.nop.core.Content;
import org.nop.core.ControllersFeature;
import org.nop.core.DispatcherHandler;
import org.nop.core.DispatcherRequestResult;
import org.nop.core.Request;
import org.nop.core.RequestBeanCreator;
import org.nop.templating.Template;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xthl.core.styling.CssItem;
import org.xthl.core.styling.StylingParseException;

/**
 *
 * @author Alexey Andreev
 */
public class StylingFeatureImpl implements StylingFeature {
    private static final Logger logger = LoggerFactory.getLogger(StylingFeatureImpl.class);
    private Application app;
    private Map<Class<?>, Styler> stylerMap = new HashMap<Class<?>, Styler>();
    private StylerFactory stylerFactory;

    public StylingFeatureImpl(Application app) {
        this.app = app;
        stylerFactory = new StylerFactory(app);
        app.registerRequest(PageResourceProvider.class,
                new RequestBeanCreator<PageResourceProvider>() {
            @Override
            public PageResourceProvider createBean(Request context) {
                return (PageResourceProvider)context.get(PageResourceConsumer.class);
            }
        });
        app.registerClass(PageResourceConsumer.class, PageResourceSetImpl.class);
        app.get(ControllersFeature.class).addDispatcherHandler(new DispatcherHandler() {
            @Override
            public void beforeRequest(Request request) {
            }
            
            @Override
            public void afterRequest(DispatcherRequestResult request) {
                Content content = request.getContent();
                if (content != null && content.getView() != null) {
                    addResourcesFromView(request, content.getView());
                }
            }
        });
    }

    @Override
    public void addStyler(Class<?> baseClass, String resourceName) {
        if (!app.isLoading()) {
            throw new IllegalStateException("Application already loaded");
        }
        String fullName = baseClass.getPackage().getName() + "." + resourceName;
        logger.info("Styler registered: " + fullName);
        Styler styler;
        try {
            styler = stylerFactory.createStyler(baseClass, resourceName);
        } catch (StylingParseException ex) {
            throw new RuntimeException("Error creating styler " + fullName, ex);
        }
        for (Class<?> viewType : styler.getSupportedViews()) {
            stylerMap.put(viewType, styler);
        }
    }
    
    public Set<CssItem> getCss(Class<? extends Template> viewType) {
        Set<CssItem> items = new HashSet<CssItem>();
        for (Class<? extends Template> view : findViewTypes(viewType)) {
            Styler styler = stylerMap.get(view);
            if (styler != null) {
                items.addAll(styler.getStyles(view));
            }
        }
        return items;
    }

    public Set<String> getScripts(Class<? extends Template> viewType) {
        Set<String> items = new HashSet<String>();
        for (Class<? extends Template> view : findViewTypes(viewType)) {
            Styler styler = stylerMap.get(view);
            if (styler != null) {
                items.addAll(styler.getScripts(view));
            }
        }
        return items;
    }
    
    private Set<Class<? extends Template>> findViewTypes(Class<? extends Template> baseViewType) {
        Set<Class<? extends Template>> result = new HashSet<Class<? extends Template>>();
        Queue<Class<? extends Template>> queue = new ArrayDeque<Class<? extends Template>>();
        queue.add(baseViewType);
        while (!queue.isEmpty()) {
            Class<? extends Template> viewType = queue.remove();
            if (result.add(viewType)) {
                Styler styler = stylerMap.get(viewType);
                if (styler != null) {
                    queue.addAll(styler.getDependantTypes(viewType));
                }
            }
        }
        return result;
    }
    
    private void addResourcesFromView(Request request, Template view) {
        PageResourceConsumer consumer = request.get(PageResourceConsumer.class);
        for (Class<?> iface : view.getClass().getInterfaces()) {
            if (Template.class.isAssignableFrom(iface)) {
                for (CssItem item : getCss(iface.asSubclass(Template.class))) {
                    consumer.addCss(item);
                }
                for (String script : getScripts(iface.asSubclass(Template.class))) {
                    consumer.addScript(script);
                }
            }
        }
    }
}
